Laboratorul 1. Concepte Fundamentale ale Limbajelor de Programare

1. Introducere

1.1 Prezentare generală a limbajului C#

C# (pronunțat în engleză "See Sharp") este un limbaj de programare modern, orientat pe obiecte, de tipul type-safe. C# permite utilizatorilor să dezvolte o mare varietate de aplicații sigure și robuste care rulează în mediul .NET. C# își are rădăcinile în familia de limbaje C și va fi imediat familiar programatorilor de C, C++, Java și JavaScript. Acest tur oferă o privire de ansamblu asupra componentelor majore ale limbajului în C# 8 și versiuni anterioare. Dacă doriți să explorați limbajul prin exemple interactive, încercați introducerea în tutorialele C#.

C# este un limbaj de programare orientat pe obiecte și pe componente. C# oferă construcții de limbaj pentru a sprijini direct aceste concepte, făcând C# un limbaj natural în care se poate crea și să utiliza componente software. Încă de la origine, C# a adăugat funcții pentru a sprijini noile sarcini de lucru și practicile în curs de dezvoltare pentru proiectare de software. La baza acestuia, C# este un limbaj orientat pe obiecte. Utilizatorul poate să definească tipurile și comportamentul lor.

Multe caracteristici C# ajută la crearea de aplicații robuste și durabile. Colectarea de garbage recuperează automat memoria ocupată de obiectele neutilizate inaccesibile. Tipurile nullable protejează împotriva variabilelor care nu se referă la obiectele alocate. Gestionarea excepțiilor oferă o abordare structurată și extensibilă pentru detectarea și recuperarea erorilor. Expresiile de tip lambda acceptă tehnici de programare funcțională. Sintaxa Language Integrated Query (LINQ) creează un șablon comun pentru lucrul cu date din orice sursă. Suportul pentru operațiuni asincrone oferă sintaxă pentru construirea de sisteme distribuite. C# are un sistem de tip unificat. Toate tipurile C#, inclusiv tipurile primitive, cum ar fi int și double, moștenesc de la un singur tip de obiect rădăcină. Toate tipurile au în comun un set de operațiuni comune. Valorile de orice tip pot fi stocate, transportate și operate într-o manieră constantă. În plus, C# acceptă atât tipurile de referință definite de utilizator, cât și tipurile de valori. C# permite alocarea dinamică a obiectelor și stocarea in-line a structurilor simple. C# acceptă metode și tipuri generice, care oferă siguranță și performanțe sporite. C# oferă iteratori, care permit implementatorilor claselor de colecție să definească comportamente personalizate pentru codul clientului.

C# pune accent pe administrarea versiunilor pentru a ne asigura că programele și bibliotecile pot evolua în timp într-o manieră compatibilă. Aspectele designului C# care au fost direct influențate de considerentele de versiuni includ modificatorii separați virtuali și de suprascriere, regulile pentru rezoluția supraîncărcării metodei și suportul pentru declarațiile explicite ale membrilor interfeței.

1.2 Arhitectura .NET

Programele C# rulează pe .NET, un sistem de execuție virtual numit Common Language Runtime (CLR) și un set de biblioteci de clase. CLR este implementarea de către Microsoft a Common Language Infrastructure (CLI), un standard internațional. CLI este baza pentru crearea de medii de execuție și dezvoltare în care limbile și bibliotecile lucrează împreună fără probleme.

Codul sursă scris în C# este compilat într-un limbaj intermediar (IL - Intermediat Language) care este conform cu specificația CLI. Codul IL și resursele, precum bitmaps și strings, sunt stocate într-un ansamblu, de obicei cu o extensie de tip .dll. Un ansamblu conține un manifest care oferă informații despre tipurile, versiunea și cultura ansamblului.

Când programul C# este executat, ansamblul este încărcat în CLR. CLR realizează compilarea Just-In-Time (JIT) pentru a converti codul IL în instrucțiuni native ale mașinii. CLR oferă alte servicii legate de colectarea automată a gunoiului, gestionarea excepțiilor și gestionarea resurselor. Codul care este executat de CLR este uneori denumit ”managed code”. ”Unmanaged code” este compilat într-un limbaj de mașină nativ care vizează o anumită platformă.

Interoperabilitatea limbajului este o caracteristică cheie a .NET. Codul IL produs de compilatorul C# este conform cu specificația de tip comun (CTS). Codul IL generat din C# poate interacționa cu codul care a fost generat din versiunile .NET ale F#, Visual Basic, C++. Există peste 20 de alte limbaje compatibile cu CTS. Un singur ansamblu poate conține mai multe module scrise în diferite limbaje .NET. Tipurile se pot referi unul la altul ca și cum ar fi fost scrise în aceeași limbaj.

Pe lângă serviciile de rulare, .NET include și biblioteci extinse. Aceste biblioteci acceptă multe sarcini de lucru diferite. Sunt organizate în spatii de nume - "namespace-uri" care oferă o mare varietate de funcționalități utile. Bibliotecile includ tot, de la intrarea și ieșirea fișierelor la manipularea șirurilor de caractere la analiza XML, la "framework-uri" de aplicații web la controalele Windows Forms. O aplicație generică C# folosește biblioteca de clase .NET pe scară largă pentru a se ocupa de task-urile obișnuite de inițiere.

Pentru mai multe informații despre .NET, consultați Prezentarea generală a .NET.

1.3 Programul "Hello world"

Programul "Hello, World" este folosit în mod tradițional pentru a introduce un limbaj de programare. Aici este în C#:

using System;

class Hello
{
    static void Main()
    {
        Console.WriteLine("Hello, World");
    }
}

Programul "Hello, World" începe cu directiva using, care face referire la namespace-ul System. Namespace-urile oferă un mijloc ierarhic de organizare a programelor și bibliotecilor C#. Namespace-urile conțin tipuri și alte spații de nume — de exemplu, namespace-ul System conține un număr de tipuri, cum ar fi clasa Console la care se face referire în program și o serie de namespace-uri, cum ar fi IO și Collections. Directiva "using" care face referire la un anumit namespace permite utilizarea necalificată a tipurilor care sunt membre acelui namespace. Datorită directivei using, programul poate folosi Console.WriteLine ca prescurtare pentru System.Console.WriteLine.

Clasa Hello declarată de programul "Hello, World" are un singur membru, metoda numită Main. Metoda Main este declarată cu modificatorul static. În timp ce metodele de instanță pot face referire la o anumită instanță de obiect care înglobează folosind cuvântul cheie this, metodele statice funcționează fără referire la un anumit obiect. Prin convenție, o metodă statică numită Main servește ca punct de intrare al unui program C#.

Ieșirea programului este produsă de metoda WriteLine a clasei Console din namespace-ul System. Această clasă este furnizată de bibliotecile de clase standard, care, implicit, sunt referite automat de către compilator.

1.4 Tipuri și variabile

Un tip definește structura și comportamentul oricăror date în C#. Declarația unui tip poate include membrii săi, tipul de bază, interfețele pe care le implementează și operațiunile permise pentru acel tip. O variabilă este o etichetă sau un nume care se referă la o instanță de un anumit tip.

Există două categorii de tipuri în C#: tipuri de valoare și tipuri de referință. Variabilele tipurilor de valori conțin direct datele lor. Variabilele tipurilor de referință stochează referințe la datele lor, acestea din urmă fiind cunoscute ca obiecte. Cu tipurile de referință, este posibil ca două variabile să facă referire la același obiect și este posibil ca operațiunile pe o variabilă să afecteze obiectul referit de cealaltă variabilă. Cu tipurile de valori, variabilele au fiecare propria copie a datelor și nu este posibil ca operațiunile pe una să o afecteze pe cealaltă (cu excepția variabilelor cu parametrii ref și out).

Un identificator este un nume de variabilă. Un identificator este o secvență de caractere unicode fără spații. Un identificator poate fi un cuvânt rezervat C#, dacă este precedat de @. Utilizarea unui cuvânt rezervat ca identificator poate fi utilă atunci când interacționați cu alte limbaje.

Tipurile value sau valoare în C# sunt împărțite în continuare în tipuri simple, tipuri de enumerare, tipuri de structuri, tipuri de valori nullable și tipuri de valori tuple. Tipurile de referință C# sunt împărțite în continuare în tipuri de clasă, tipuri de interfețe, tipuri de vector și tipuri de delegati.

Următoarea schiță oferă o prezentare generală a sistemului de tip C#.
Tipuri value
Tipuri simple
Întreg cu semn: sbyte, short, int, long
Întreg fără semn: octet, ushort, uint, ulong
Caractere Unicode: char, care reprezintă o unitate de cod UTF-16
IEEE binar cu virgulă mobilă: float, double
Virgula mobilă zecimală cu precizie mare: decimal
Boolean: bool, care reprezintă valori booleene — valori care sunt fie adevărate, fie false
Tipuri enum
Tipuri definite de utilizator sum formă de de forma enumerare E {...}. Un tip de enumerare este un tip distinct cu constante numite. Fiecare tip de enumerare are un tip la bază, care trebuie să fie unul dintre cele opt tipuri integrale. Setul de valori ale unui tip de enumerare este același cu setul de valori ale tipului de bază.
Tipuri de structuri: struct
Tipuri definite de utilizator sub formă de struct S {...}
Tipuri de valori nullable
Extensii ale tuturor celorlalte tipuri de valori cu o valoare null
Tipuri Tuple value
Tipuri sub formă definite de utilizator: form (T1, T2, ...)
Tipuri de referință: reference
Tipuri de clasă: class
Clasa de bază finală a tuturor celorlalte tipuri: obiect
String Unicode: șir, care reprezintă o secvență de unități de cod UTF-16
Tipuri definite de utilizator sub formă de clasă: class C {...}
Tipuri de interfață
Tipuri definite de utilizator sub formă de interfață: interface I {...}
Tipuri de vector
Unidimensional, multidimensional și ”jagged”. De exemplu: int[], int[,] și int[][]
Tipuri de delegați
Tipuri definite de utilizator sub formă de delegat int D(...)

Programele C# folosesc declarații type pentru a crea tipuri noi. O declarație type specifică numele și membrii noului tip. Șase dintre categoriile type ale C# pot fi definite de utilizator: tipuri de clasă, tipuri de structuri, tipuri de interfețe, tipuri de enumerare, tipuri de delegat și tipuri de valori tuple. De asemenea, puteți declara tipuri de înregistrări, fie structuri de înregistrări, fie clase de înregistrări. Tipurile de înregistrare au membri sintetizați de compilator. De obicei, utilizăm înregistrări pentru stocarea valorilor, cu un comportament asociat minim.

Un tip de clasă definește o structură de date care conține membri de date (câmpuri) și membri de funcție (metode, proprietăți și altele). Tipurile de clasă acceptă moștenirea unică și polimorfismul, mecanisme prin care clasele derivate pot extinde și specializa clasele de bază. Un tip struct este similar cu un tip de clasă prin aceea că reprezintă o structură cu membri de date și membri de funcție. Cu toate acestea, spre deosebire de clase, structurile sunt tipuri de valori și, de obicei, nu necesită alocarea heap. Tipurile de structuri nu acceptă moștenirea specificată de utilizator și toate tipurile de structuri moștenesc implicit de la clasa ValueType.

Un tip de interfață definește un contract ca un set numit de membri publici. O clasă sau o structură care implementează o interfață trebuie să furnizeze implementări ale membrilor interfeței. O interfață poate moșteni de la mai multe interfețe de bază, iar o clasă sau o structură poate implementa mai multe interfețe.

Un tip delegat reprezintă referințe la metode cu o anumită listă de parametri și un tip de retur. Delegații fac posibilă tratarea metodelor ca entități care pot fi atribuite variabilelor și transmise ca parametri. Delegații sunt analogi cu tipurile de funcții furnizate de limbaje funcționale. Ele sunt, de asemenea, similare cu conceptul de pointeri la funcții din alte limbaje. Spre deosebire de pointerii la funcții, delegații sunt orientați pe obiecte și au un tip dedicat.

Tipurile de clasă, structură, interfață și delegat toate acceptă parametri generici, prin care pot fi parametrizate cu alte tipuri.

C# acceptă matrici unidimensionale și multidimensionale de orice tip. Spre deosebire de tipurile enumerate mai sus, tipurile de matrici nu trebuie să fie declarate înainte de a putea fi utilizate. În schimb, tipurile de vector sunt construite urmând un nume de tip cu paranteze drepte. De exemplu, int[] este o vector unidimensională de int, int[,] este o vector bidimensională a int, iar int[][] este o vector unidimensională de vector unidimensionale sau un „jagged " vector, de int.

Tipurile nullabile nu necesită o definiție separată. Pentru fiecare tip T care nu poate fi anulat, există un tip care nu poate fi anulat T?, care poate conține o valoare suplimentară, nulă. De exemplu, int? este un tip care poate conține orice număr întreg de 32 de biți sau valoarea nulă și șir? este un tip care poate conține orice șir sau valoarea null.

Sistemul de tipuri C# este unificat astfel încât o valoare de orice tip poate fi tratată ca un obiect. Fiecare tip din C# derivă direct sau indirect din tipul clasei Object, iar Object este clasa de bază finală a tuturor tipurilor. Valorile tipurilor de referință sunt tratate ca obiecte prin simpla vizualizare a valorilor ca obiect tip. Valorile tipurilor de valori sunt tratate ca obiecte prin efectuarea de operațiuni de box și unboxing. În exemplul următor, o valoare int este convertită în obiect și înapoi din nou în int.

int i = 123;
object o = i;    // Împachetare
int j = (int)o;  // Despachetare

Când o valoare a unui tip de valoare este atribuită unei referințe de obiect, o „casetă” este alocată pentru a păstra valoarea. Acea casetă este o instanță a unui tip de referință, iar valoarea este copiată în acea casetă. În schimb, atunci când o referință de obiect este un cast la un value type, se verifică dacă obiectul referit este o casetă de tipul de valoare corect. Dacă verificarea reușește, valoarea din casetă este copiată în tipul de valoare.

Sistemul de tipuri unificat al C# înseamnă efectiv că tipurile de valori sunt tratate ca referințe la obiecte la cerere. Datorită unificării, bibliotecile de uz general care utilizează tip obiect pot fi utilizate cu toate tipurile care derivă din obiect, inclusiv tipurile de referință și tipurile de valoare.

Există mai multe tipuri de variabile în C#, inclusiv câmpuri, elemente de vector, variabile locale și parametri. Variabilele reprezintă locații de depozitare. Fiecare variabilă are un tip care determină ce valori pot fi stocate în variabilă, așa cum se arată mai jos.
Tip valoare non-nullable
O valoare de exact același tip
Tip valoare nullable
O valoare nulă sau o valoare de acel tip exact
Obiect
O referință nulă, o referință la un obiect de orice tip de referință sau o referire la o valoare în casetă de orice tip de valoare
Tip clasă
O referință nulă, o referință la o instanță a acelui tip de clasă sau o referire la o instanță a unei clase derivate din acel tip de clasă
Tip interfață
O referință nulă, o referință la o instanță a unui tip de clasă care implementează acel tip de interfață sau o referință la o valoare în casetă a unui tip de valoare care implementează acel tip de interfață
Tip vector
O referință nulă, o referință la o instanță a acelui tip de vector sau o referire la o instanță a unui tip de vector compatibil
Tip delegat
O referință nulă sau o referință la o instanță a unui tip de delegat compatibil

1.5 Structura programului

Conceptele organizaționale cheie în C# sunt programele, namespace-urile, tipurile, membrii și ansamblurile. Programele declară tipuri, care conțin membri și pot fi organizate în spații de nume. Clasele, structurile și interfețele sunt exemple de tipuri. Câmpurile, metodele, proprietățile și evenimentele sunt exemple de membri. Când programele C# sunt compilate, acestea sunt ambalate fizic în ansamble. Ansamblele au, de obicei, extensia de fișier .exe sau .dll, dacă implementează aplicații sau, respectiv, biblioteci.

Ca un mic exemplu, luați în considerare un ansamblu care conține următorul cod:

namespace Acme.Collections;

public class Stack
{
    Entry _top;

    public void Push(T data)
    {
        _top = new Entry(_top, data);
    }

    public T Pop()
    {
        if (_top == null)
        {
            throw new InvalidOperationException();
        }
        T result = _top.Data;
        _top = _top.Next;

        return result;
    }

    class Entry
    {
        public Entry Next { get; set; }
        public T Data { get; set; }

        public Entry(Entry next, T data)
        {
            Next = next;
            Data = data;
        }
    }
}

Numele complet calificat al acestei clase este Acme.Collections.Stack. Clasa conține mai mulți membri: un câmp numit _top, două metode numite Push și Pop și o clasă imbricată numită Entry. Clasa Entry mai conține trei membri: o proprietate numită Next, o proprietate numită Data și un constructor. Stiva este o clasă generică. Are un parametru de tip, T care este înlocuit cu un tip concret atunci când este utilizata.

O stivă este o colecție „ultimul intrat – primul ieşit” (LIFO). Sunt adăugate elemente noi în partea de sus a stivei. Când un element este îndepărtat, acesta este îndepărtat din partea de sus a stivei. Exemplul anterior declară tipul de stivă care definește stocarea și comportamentul unei stive. Puteți declara o variabilă care se referă la o instanță de tipul Stivă pentru a utiliza acea funcționalitate.

Ansamblurile conțin cod executabil sub formă de instrucțiuni Intermediate Language (IL) și informații simbolice sub formă de metadata. Înainte de a fi executat, compilatorul Just-In-Time (JIT) al .NET Common Language Runtime convertește codul IL într-un ansamblu în cod specific procesorului.

Deoarece un ansamblu este o unitate de funcționalitate care se descrie automat, care conține atât cod, cât și metadata, nu este nevoie de directiva #include și fișiere de antet în C#. Tipurile și membrii publici conținute într-un anumit ansamblu sunt puse la dispoziție într-un program C# prin simpla referire la acel ansamblu la compilarea programului. De exemplu, acest program folosește clasa Acme.Collections.Stack din ansamblul acme.dll:

class Example
{
    public static void Main()
    {
        var s = new Acme.Collections.Stack();
        s.Push(1); // stiva conține 1
        s.Push(10); // stiva conține 1, 10
        s.Push(100); // stiva conține 1, 10, 100
        Console.WriteLine(s.Pop()); // stiva conține 1, 10
        Console.WriteLine(s.Pop()); // stiva conține 1
        Console.WriteLine(s.Pop()); // stiva este goală
    }
}

Pentru a compila acest program, ar trebui să faceți referire la ansamblul care conține clasa stivă definită în exemplul anterior.

Programele C# pot fi stocate în mai multe fișiere sursă. Când un program C# este compilat, toate fișierele sursă sunt procesate împreună, iar fișierele sursă se pot referi liber unul la altul. Din punct de vedere conceptual, este ca și cum toate fișierele sursă au fost concatenate într-un fișier mare înainte de a fi procesate. Declarațiile directe nu sunt niciodată necesare în C# deoarece, cu puține excepții, ordinea declarațiilor este nesemnificativă. C# nu limitează un fișier sursă la declararea unui singur tip public și nici nu necesită ca numele fișierului sursă să se potrivească cu un tip declarat în fișierul sursă.

1.6 Sarcini

TODO

Bibliografie

[1] Microsoft Corporation. C# Documentation, https://docs.microsoft.com/en-us/dotnet/csharp/, 2022.